#ifndef SIMPLE_IO_ADDRESS_H
#define SIMPLE_IO_ADDRESS_H

#include <string> // std::string
#include <variant> // std::variant
#include <cstdlib> // uintptr_t

#include "simple/support/meta/pend.hpp" // support::meta::prepend_t

#include "handle_common.h" // custom_unique_ptr
#include "error.h" // error

namespace simple::io
{
	namespace meta = support::meta;

	enum class address_handle : uintptr_t {};

	struct free_address_config
	{
		using handle_t = address_handle;
		static handle_t null() noexcept;
		static void deleter(handle_t) noexcept;
	};

	using address = custom_unique_ptr<free_address_config>;

	struct address_error : error
	{
		int code;
	};

	struct address_try_again : address_error {};
	struct name_unresolved : address_error {};
	struct service_unrecognized : address_error {};
	// TODO: struct socket_type_unrecognized : address_error {};
	// TODO: struct unrecognized_family : address_error {};
	// TODO: struct bad_flags : address_error {};

	using address_error_t = std::variant<
		address_try_again,
		name_unresolved,
		service_unrecognized,
		address_error
	>;
	using address_result = meta::prepend_t<address_error_t, address>;

	[[nodiscard]]
	address_result make_address(const std::string& name, const std::string& service) noexcept;

	constexpr struct make_address_f_t
	{
		template <typename... Args>
		[[nodiscard]] decltype(auto) operator()(Args&& ... args) const noexcept
		{ return simple::io::make_address(std::forward<Args>(args)...); }
	} make_address_f;

	[[nodiscard]] inline std::string get_message(const make_address_f_t&)
	{ return "simple::io::make_address"; }

	[[nodiscard]] std::string to_string(const address&);

} // namespace simple::io

#endif /* end of include guard */
